// Ninf-G-XX, ver. 0.1
// Copyright 2011 National Institute of Advanced Industrial Science and Technology
// 
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// 
//     http://www.apache.org/licenses/LICENSE-2.0
// 
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/*
 *  $Id: GenCompile.cpp,v 1.2 2009/01/06 10:43:46 nakada Exp $
 */

#include <string>
#include <vector>

#include "Gen.hpp"
#include "YYSTYPE.hpp"
#include "IDL.hpp"
#include "FpOutputStream.hpp"

using namespace std;
using namespace ninf::gen;

static DATA_TYPE expr_to_type(expr x);

static bool
compile_param_decl(vector<ParamDim *> * dimVec, expr decl,
		   FunctionEntry * entry,
		   vector<const char *> * paramNames);

void
compile_interface_getoption(expr option_list, expr * backendp, expr * reqp,
			     expr * orderp, expr * languagep, expr * shrinkp){
  GenExprList *lp;
  if (option_list == NULL) return;
  lp = EXPR_LIST(option_list);
  while (lp != NULL){
    expr tmp = lp->expr;
    if (EXPR_CODE(tmp) != LIST)
      error_at_node(tmp, "internal error: expects list");
    if (EXPR_CODE(EXPR_ARG1(tmp)) != INT_CONSTANT)
      error_at_node(tmp, "internal error: expects int");
    switch (EXPR_INT(EXPR_ARG1(tmp))){
    case REQUIRED:
      *reqp = EXPR_ARG2(tmp); break;
    case BACKEND:
      *backendp = EXPR_ARG2(tmp); break;
    case CALCORDER:
      *orderp = EXPR_ARG2(tmp); break;
    case LANGUAGE:
      *languagep = EXPR_ARG2(tmp); break;
    case SHRINK:
      *shrinkp = EXPR_ARG2(tmp); break;
    default:
      error_at_node(tmp, "internal error: unknown code");
    }
    lp = lp->next;
  }
}

void
generate_scalarParamNames(expr x, vector <const char *> * paramNames){
  GenExprList * lp = EXPR_LIST(x);
  for (; lp != NULL; lp = lp->next){
    expr x = lp->expr;

    // if mode == in and scalar
    if (EXPR_INT(EXPR_ARG1(EXPR_ARG1(x))) == (int)MODE_IN &&
        EXPR_ARG2(x)->code == IDENT)
      paramNames->push_back(EXPR_SYM(EXPR_ARG2(x))->name);
    else
      paramNames->push_back("");
  }
}


ParamDesc * 
compile_param(expr param, 
	      FunctionEntry * entry, 
	      vector <const char *> * paramNames );

bool
generate_paramDescs(expr x, FunctionEntry * entry, 
		    vector <const char *> * paramNames){
  GenExprList * lp = EXPR_LIST(x);
  ParamDesc ** params = new ParamDesc * [paramNames->size()];

  for (int i = 0; lp != NULL; lp = lp->next, i++){
    expr x = lp->expr;
    params[i] = compile_param(x, entry, paramNames);
    if (params[i] == NULL)
      return false;
  }
  entry->interface.nParams = paramNames->size();
  entry->interface.params = params;
  return true;
}


void
ninf::gen::compile_interface(expr def, expr desc, expr option_list, expr body){
  FunctionEntry * entry = new FunctionEntry();

  expr backend = NULL;
  expr req = NULL;
  expr order = NULL;
  expr language = NULL;
  expr shrink = NULL;

  compile_interface_getoption(option_list, &backend, &req, &order, 
			      &language, &shrink);
  if (ncGenGlobals.debug){
    printf(  "Define:");    def->print(stdout);
    printf("\n\tDesc:");    desc->print(stdout);
    printf("\n\trequired:");req->print(stdout);
    printf("\n\tbody:");    body->print(stdout);
    printf("\n");
  }

  generate_scalarParamNames(EXPR_ARG2(def), &(entry->scalarParamNames));

  if (! generate_paramDescs(EXPR_ARG2(def), 
			    entry,  
			    &(entry->scalarParamNames))){
    error("something happened while parsing the syntax tree");
  }

  if (desc != NULL) entry->interface.description = EXPR_STR(desc);
  if (req != NULL) entry->required = EXPR_STR(req);

  if (EXPR_CODE(body) == STRING_CONSTANT)
    entry->body = EXPR_STR(body);
  else 
    entry->bodyExpr = body;

  if (language != NULL) 
    entry->language = EXPR_STR(language);
  else if (entry->bodyExpr != NULL && 
	   EXPR_ARG1(entry->bodyExpr) != NULL)
    entry->language = EXPR_STR(EXPR_ARG1(entry->bodyExpr));
  else
    entry->language = NULL;
  
  if (shrink != NULL && strncasecmp(EXPR_STR(shrink), "yes", 4) == 0)
    entry->interface.shrink = TRUE;

  entry->interface.moduleName = ncGenGlobals.currentModule;
  entry->interface.entryName = (EXPR_SYM(EXPR_ARG1(def)))->name;
  
  //entry->interface.writeDeclaration(0, 1, stdout);
  //  FpOutputStream tmp(stdout);
  //  DataOutputStream dos(&tmp);
  //  entry->interface.writeTo(dos);

  ncGenGlobals.stubs.push_back(entry);
}


int 
find_in_scalar(const char * sym, vector <const char *> * paramNames){
  for (unsigned int i = 0; i < paramNames->size(); i++){
    if (strcmp(sym, (*paramNames)[i]) == 0)
      return i;
  }
  return -1;
}

int 
getOpCode(GenExprCode code){
  if (code == PLUS_EXPR)         return OP_VALUE_PLUS;
  if (code == MINUS_EXPR)        return OP_VALUE_MINUS;
  if (code == UNARY_MINUS_EXPR)  return OP_VALUE_UN_MINUS;
  if (code == MUL_EXPR)          return OP_VALUE_MUL;
  if (code == BEKI_EXPR)         return OP_VALUE_BEKI;
  if (code == DIV_EXPR)          return OP_VALUE_DIV;
  if (code == MOD_EXPR)          return OP_VALUE_MOD;
  
  if (code == EQ_EXPR)           return OP_VALUE_EQ;
  if (code == NEQ_EXPR)          return OP_VALUE_NEQ;
  if (code == GT_EXPR)           return OP_VALUE_GT;
  if (code == LT_EXPR)           return OP_VALUE_LT;
  if (code == GE_EXPR)           return OP_VALUE_GE;
  if (code == LE_EXPR)           return OP_VALUE_LE;
  if (code == TRY_EXPR)          return OP_VALUE_TRY;
  return -1;
}


bool
compile_expression(GenExpr * x,
		   vector<ExpEntry *> * expVec,
		   vector <const char *> * paramNames){
  switch (EXPR_CODE(x)){
  case INT_CONSTANT:
    expVec->push_back(new ExpEntry(EXPR_INT(x)));
    return true;

  case IDENT:
    {
      int i = find_in_scalar(EXPR_SYM(x)->name, paramNames);
      if (i < 0){
	error_at_node(x,"input parameter is not found, %s",
		      SYM_NAME(EXPR_SYM(x)));
	return false;
      }
      expVec->push_back(new ExpEntry(VALUE_IN_ARG, i));
      return true;
    }
  case PLUS_EXPR:  case MINUS_EXPR:  case MUL_EXPR:  case BEKI_EXPR:
  case DIV_EXPR:   case MOD_EXPR:    case EQ_EXPR:   case NEQ_EXPR:
  case GT_EXPR:    case LT_EXPR:     case GE_EXPR:   case LE_EXPR:
    {
      if (!compile_expression(EXPR_ARG1(x), expVec, paramNames)) return false;
      if (!compile_expression(EXPR_ARG2(x), expVec, paramNames)) return false;
      int code = getOpCode(EXPR_CODE(x));
      if (code < 0) return false;
      expVec->push_back(new ExpEntry(VALUE_OP, code));
      return true;
    }
  case TRY_EXPR:
    {
      if (!compile_expression(EXPR_ARG1(x), expVec, paramNames)) return false;
      if (!compile_expression(EXPR_ARG2(x), expVec, paramNames)) return false;
      if (!compile_expression(EXPR_ARG3(x), expVec, paramNames)) return false;
      int code = getOpCode(EXPR_CODE(x));
      if (code < 0) return false;
      expVec->push_back(new ExpEntry(VALUE_OP, code));
      return true;
    }
  case UNARY_MINUS_EXPR:
    {
      if (!compile_expression(EXPR_ARG1(x), expVec, paramNames)) return false;
      int code = getOpCode(EXPR_CODE(x));
      if (code < 0) return false;
      expVec->push_back(new ExpEntry(VALUE_OP, code));
      return true;
    }
  default:
    return false;
  }
}


Expression *
compile_expression(GenExpr * expr, vector <const char *> * paramNames){
  if (expr == NULL)
    return new Expression(0, NULL);
  vector<ExpEntry *> expVec;
  if (!compile_expression(expr, &expVec, paramNames))
    return NULL;

  ExpEntry * entries = new ExpEntry[expVec.size()];
  for (unsigned int i = 0; i < expVec.size(); i++){
    entries[i] = *(expVec[i]);
  }
  return new Expression(expVec.size(), entries);
}

/* param = (LIST (LIST mode type) decl) */
/* returns parameter counts: usualy returns 1.
              for callback function, returns param+1  */
ParamDesc * 
compile_param(expr              param, 
	      FunctionEntry * entry, 
	      vector <const char *> * paramNames )
{     
    GenExpr * mode, * type;
    DataType dataType;
    ModeType modeType;

    if(param == NULL) 
      return NULL;
    mode = EXPR_ARG1(EXPR_ARG1(param));
    if(mode != NULL) 
	modeType = EXPR_MODE_SPEC(mode);

    type = EXPR_ARG2(EXPR_ARG1(param));
    if(type == NULL) 
      error_at_node(param,"no parameter type");
    else 
      dataType = expr_to_type(type);

    vector <ParamDim *> dimVec;
    if (!compile_param_decl(& dimVec, EXPR_ARG2(param), entry, paramNames))
      return NULL;

    ParamDim ** dim = new ParamDim *[dimVec.size()];
    for (unsigned int i = 0; i < dimVec.size(); i++){
      dim[i] = dimVec[i];
    }
    return new ParamDesc(dataType, modeType, 
			   dimVec.size(), dim);
}

/* decl := ident | (ARRAY_REF expr range)) */
/* return the dimension */
static bool
compile_param_decl(vector<ParamDim *> * dimVec, 
		   expr                   decl,
		   FunctionEntry *      entry, 
		   vector<const char *> *       paramNames)
{
    switch(EXPR_CODE(decl)){
    case IDENT:
       entry->paramNames.push_back(EXPR_SYM(decl)->name);
       return true;
    case ARRAY_REF:
      {
	Expression * size  = 
	  compile_expression(EXPR_ARG2(decl), paramNames);
	Expression * start, * end, * step;
	if (EXPR_ARG3(decl) == NULL){
	  start = new Expression(0, NULL);
	  end   = new Expression(0, NULL);
	  step  = new Expression(0, NULL);
	} else {
	  start = compile_expression(EXPR_ARG1(EXPR_ARG3(decl)), paramNames);
	  end   = compile_expression(EXPR_ARG2(EXPR_ARG3(decl)), paramNames);
	  step  = compile_expression(EXPR_ARG3(EXPR_ARG3(decl)), paramNames);
	}
	
	ParamDim * dim = new ParamDim(size, start, end, step);
	
	dimVec->push_back(dim);
	return (compile_param_decl(dimVec, EXPR_ARG1(decl), entry, paramNames));
      }
    case POINTER_REF:	
      {
	ExpEntry * size_entries = new ExpEntry[1];
	size_entries[0] = ExpEntry(VALUE_CONST, 1);

	ParamDim * dim = 
	  new ParamDim(
			 new Expression(1, size_entries),
			 new Expression(0, NULL),
			 new Expression(0, NULL),
			 new Expression(0, NULL));
	
	if(EXPR_CODE(EXPR_ARG1(decl)) != IDENT)
	  error_at_node(decl,"bad pointer declaration");

	dimVec->push_back(dim);
	return (compile_param_decl(dimVec, EXPR_ARG1(decl), entry, paramNames));
	
	/* !!! NOTE: other declaration is not implemented yet !!! */
      }
    default:
      error_at_node(decl,"unkonwn declarator");
      return(0);
    }
}


void
ninf::gen::compile_MODULE(expr x)
{
  if (ncGenGlobals.debug) {
    fprintf(stderr, "Module:");
    x->print(stdout);
    printf("\n");
  }
  if (ncGenGlobals.currentModule != NULL){
    error_at_node(x,"module name is already defined");
    return;
  }
  if(EXPR_CODE(x) == IDENT)
    ncGenGlobals.currentModule = SYM_NAME(EXPR_SYM(x));
  else if(EXPR_CODE(x) == STRING_CONSTANT)
    ncGenGlobals.currentModule = EXPR_STR(x);
  else 
    error_at_node(x,"illegal module name");
}

void
ninf::gen::compile_FORTRANFORMAT(expr x)
{
  if(EXPR_CODE(x) != STRING_CONSTANT) fatal("FORTRANFORMAT");
  ncGenGlobals.currentFortranFormat = EXPR_STR(x);
}

void
ninf::gen::compile_FORTRANSTRINGCONVENTION(expr x)
{
  const char * name;
  if(EXPR_CODE(x) != IDENT)
    fatal("\"FortranStringConvention\" should be \"last\" or \"justAfter\"");

  name = SYM_NAME(EXPR_SYM(x));
  if (strcmp("last", name) == 0)
    ncGenGlobals.fortranStringConvention = FORTRAN_STRING_CONVENTION_LAST;
  else if (strcmp("justAfter", name) == 0)
    ncGenGlobals.fortranStringConvention = FORTRAN_STRING_CONVENTION_JUST_AFTER;
  else
    fatal("\"FortranStringConvention\" should be \"last\" of \"justAfter\"");
}

void
ninf::gen::compile_COMPILE_OPTIONS(expr x)
{
  if (ncGenGlobals.debug){
    printf("CompileOptions:");
    x->print(stdout);
    printf("\n");
  }
  if (EXPR_CODE(x) != STRING_CONSTANT) fatal("COMPILE_OPTIONS");
  ncGenGlobals.compileOptions.push_back(EXPR_STR(x));
}

void
ninf::gen::compile_GLOBALS(expr x)
{
  if (ncGenGlobals.debug){
    printf("Globals:");
    x->print(stdout);
    printf("\n");
  }
  if (EXPR_CODE(x) != STRING_CONSTANT) fatal("GLBOALS");
  ncGenGlobals.globals.push_back(EXPR_STR(x));
}

expr ninf::gen::compile_required(expr x){
  return list2(LIST, make_enode(INT_CONSTANT, REQUIRED), x);
}
expr ninf::gen::compile_backend(expr x){
  return list2(LIST, make_enode(INT_CONSTANT, BACKEND), x);
}
expr ninf::gen::compile_shrink(expr x){
  return list2(LIST, make_enode(INT_CONSTANT, SHRINK), x);
}
expr ninf::gen::compile_language(expr x){
  return list2(LIST, make_enode(INT_CONSTANT, LANGUAGE), x);
}
expr ninf::gen::compile_calcorder(expr x){
  return list2(LIST, make_enode(INT_CONSTANT, CALCORDER), x);
}

void
ninf::gen::compile_LIBRARY(expr x)
{
  if (ncGenGlobals.debug){
    printf("Library:");
    x->print(stdout);
    printf("\n");
  }
  if (EXPR_CODE(x) != STRING_CONSTANT) fatal("LIBRARY");
  ncGenGlobals.libs.push_back(EXPR_STR(x));
}

static DATA_TYPE combine_type(expr x, DATA_TYPE t2);

static DATA_TYPE expr_to_type(expr x)
{
  if (EXPR_CODE(x) == BASIC_TYPE_NODE)
    return(EXPR_TYPE(x));
  else if (EXPR_CODE(x) == LIST)
    return(combine_type(EXPR_ARG1(x),expr_to_type(EXPR_ARG2(x))));
  else fatal("illegal type");
  return (DATA_TYPE)0; // to avoid warning
}

static DATA_TYPE combine_type(expr x, DATA_TYPE t2)
{
  DATA_TYPE t1,t;
  
  if (EXPR_CODE(x) != BASIC_TYPE_NODE) goto err;
  t1 = EXPR_TYPE(x);
  t = DT_UNDEF;
  if (t1 == DT_UNDEF || t2 == DT_UNDEF) goto ok;	/* don't care */
  
  switch(t1){
  default: 
    goto err;
  case DT_LONG:	/* size spec */
    switch(t2){
    case DT_FLOAT:	t = DT_DOUBLE; goto ok;
    case DT_INT: t = DT_LONG; goto ok;
    default:
      break;

    }
    break;
  case DT_SHORT:	/* size spec */
    switch(t2){
    case DT_INT: t = DT_SHORT; goto ok;
    default:
      break;
    }
    break;
  }
err:
  error_at_node(x,"illegal type combination");
  t = DT_UNDEF;
ok:
  return(t);
}

extern char *basic_type_name(DATA_TYPE type);
extern char *mode_spec_name(MODE_SPEC mode);


